/*
  Copyright (C) 2013 Napspirit / Thierry Rochelet

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.napspirit.tmx.data;

import java.io.*;
import java.util.*;

import com.napspirit.tmx.NapTMX;
import com.napspirit.tmx.util.DateISO8601;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Text;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.OutputKeys;

import java.io.File;

import java.util.Calendar;
import java.util.GregorianCalendar;



/**
 * Storage of TMX stuff !!
 */
public class TMX {

    /** All the registered locales stuff */
    private Vector<Locale> locales;

    /** All the registered catagory */
    private Vector<String> categories;

    /** All the Translation Unid tu) */
    private Vector<TMXTU> tus;

    /** Currently selected TMXTU */
    private TMXTU current;

    /** Listener */
    private Vector<TMXListener> listeners;
    private boolean enableListener = true;

    private boolean modified;

    private String filename;

    private Locale defaultLocale; 

    private Calendar creation;

    public TMX() {
        this.init();
        this.resetListener();
    }

    public void init () {
        this.filename = null;
        this.modified = false;
        this.defaultLocale = null;
        this.locales = new Vector<Locale>();
        this.categories = new Vector<String>();
        this.tus = new Vector<TMXTU>();
        this.current = null;
        this.creation  = GregorianCalendar.getInstance();
    }


    /* ======== LISTENER ========== */

    public void resetListener () {
        this.listeners = new Vector<TMXListener>();
    }

    public void addListener (TMXListener listener) {
        this.listeners.add(listener);
    }

    public void removeListener (TMXListener listener) {
        this.listeners.remove(listener);
    }

    protected void modified () {
        if (this.enableListener) {            
            for (TMXListener listener : this.listeners) {
                listener.TMXModified();
            }
        }
    }

    // Reset all notes
    public void resetTUNotes () {
        for (TMXTU tu : this.tus) {
            tu.setNote("");
        }
    }

    // Reset all references
    public void resetTUReferences () {
        for (TMXTU tu : this.tus) {
            tu.resetReferences();
        }
    }

    /* ======== CURRENT ========== */
    
    public TMXTU getCurrent () { return this.current; }

    public void setCurrent (TMXTU tu) { this.current = tu; }

    public void setCurrent (String id) {
        this.current = this.findID(id);
    }


    /* ======== FIND ========== */

    public TMXTU findID (String id) {
        if (id==null) {
            return null;
        }

        for (TMXTU tu : this.tus) {
            if (tu.similarID(id)) {
                return tu;
            }
        }

        return null;
    }

    public boolean hasID (String id) {
        if (id==null) {
            return false;
        }

        for (TMXTU tu : this.tus) {
            if (tu.similarID(id)) {
                return true;
            }
        }

        return false;
    }


    /* =================== CATEGORY ================= */

    public void registerCategory (String category) {
        if (!this.categories.contains(category)) {
            this.categories.add(category);
        }
    }

    public Enumeration<String> getCategories () { return this.categories.elements(); }

    /* =================== LOCALE ================= */
    
    protected void registerLocale (Locale locale) {
        if (this.locales.contains(locale)) {
            return;
        }
        this.locales.add(locale);
    }
    

    public static String getRFC3066 (Locale locale) {
        StringBuffer buf = new StringBuffer();
        buf.append(locale.getISO3Language());
        try {
            String country = locale.getISO3Country();
            if (country!=null)
                if (!country.isEmpty()) {
                    buf.append("-");
                    buf.append(country);
                }
        } catch (Exception e) {   }
        return buf.toString();
    }

    /* =================== TU ================= */

    protected void addTU (TMXTU tu) { 
        this.tus.add(tu); 
        this.modified = true;
        this.modified();
    }

    public TMXTU createTU () {
        TMXTU tu = new TMXTU(this);
        this.tus.add(tu);
        this.current = tu;

        this.modified = true;
        this.modified();

        return tu;
    }

    public TMXTU registerTU (String id,String ref) { 
        if (id==null) {
            return null;
        }
        
	if(!this.hasID(id)) {
	    TMXTU tu = this.createTU();
            tu.setID(id);
            tu.addReference(ref);
            return tu;
	}
        else {
	    TMXTU tu = this.findID(id);
            tu.addReference(ref);
            return tu;
        }
    }

    public TMXTU registerTUV (Locale locale,String id,String value,String ref) { 
        if (id==null || value==null) {
            return null;
        }

        TMXTU tu = this.registerTU(id,ref);
        tu.setLocale(locale,value);
        
        return tu;
    }


    public void deleteTU (TMXTU tu) {
        if (tu==null) {
            return;
        }

        this.tus.remove(tu);
        this.current = null;

        this.modified = true;
        this.modified();
    }

    public Enumeration<TMXTU> getTUs() {
        Collections.sort(this.tus);
        return this.tus.elements();
    }

    public boolean isModified () {
        if (this.modified) {
            return true;
        }

        for (TMXTU tu : this.tus) {
            if (tu.modified) {
                return true;
            }
        }
        return false;
    }

    /** Set all stuff as SAVED (ie. not modified) */
    private void saveAll () {
        for (TMXTU tu : this.tus) {
            tu.modified = false;            
        }
        this.modified = false;
    }

    /* ============== READ ==============*/
    
    public void load (String filename) throws Exception {
        this.init();
        this.enableListener = false;
        this.filename = filename;
        this.loadXML(filename);
        this.enableListener = true;
        this.modified();
    }

    private void loadXML (String filename) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        FileInputStream stream = new FileInputStream(filename);
        Document document = builder.parse(stream);
        
        NodeList _headers = document.getElementsByTagName("header");
        for (int i=0;i<_headers.getLength();i++) {
            Node header = _headers.item(i);

            if (header.getAttributes().getNamedItem("creationdate")!=null) {
                String date = header.getAttributes().getNamedItem("creationdate").getNodeValue();
                this.creation = DateISO8601.toCalendar(date);
            }
            
        }

        // -> tu*
        NodeList _tus = document.getElementsByTagName("tu");

        for (int i=0;i<_tus.getLength();i++) {
            Node tu = _tus.item(i);
            TMXTU.loadXML(this,tu);
        }

    }


    /* ============== WRITE ==============*/

    public String getTitle () {

        if (this.filename==null) {
            return "new TMX file";
        }
        else {
            return this.filename;

        }
    }

    public void save () throws Exception  {
        this.save(this.filename);
    }

    public void save (String filename) throws Exception  {
        this.writeXML(filename);
        this.filename = filename;
        this.saveAll();
    }


    private void writeXMLHeader (Document document,Element tmx) {
	Element _header = document.createElement("header");
	tmx.appendChild(_header);

	_header.setAttribute("creationtool",NapTMX.APPLICATION_NAME);
	_header.setAttribute("creationtoolversion",NapTMX.APPLICATION_VERSION);
	_header.setAttribute("segtype","sentence");  // block , paragraph , sentence , phrase
	_header.setAttribute("o-tmf","n/a");
	_header.setAttribute("adminlang","en-us"); // RFC 3066
	_header.setAttribute("scrlang","EN");  // RFC 3066
        _header.setAttribute("datatype","PlainText");

        _header.setAttribute("creationdate",DateISO8601.fromCalendar(this.creation));
        _header.setAttribute("changedate",DateISO8601.now());
    }

    private void writeXMLBody (Document document,Element tmx) {
	Element _body = document.createElement("body");
	tmx.appendChild(_body);
        
        for (TMXTU tu : this.tus) {
            tu.writeXML(document,_body);
        }
    }

    private void writeXML (String filename) throws Exception  {
        // create document
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.newDocument();
	
        // tmx
        Element _tmx = document.createElement("tmx");
        document.appendChild(_tmx);
        _tmx.setAttribute("version","1.4b");
        
        // tmx->header
        this.writeXMLHeader(document,_tmx);        
        // tmx->body
        this.writeXMLBody(document,_tmx);


	// Prepare to write XML
	TransformerFactory transfac = TransformerFactory.newInstance();
	Transformer trans = transfac.newTransformer();
	// trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	trans.setOutputProperty(OutputKeys.INDENT, "yes");
	trans.setOutputProperty(OutputKeys.ENCODING, "utf-8");
	    
	// Delete similar file if necessary
	File file = new File(filename);
	if(file.exists())
	    file.delete();
	    
	// Write it !
	StreamResult result =  new StreamResult(file);
	DOMSource source = new DOMSource(document);
	trans.transform(source, result);        
    }



    // =========== LOCALES =============
    public String [] getNotDefinedLocales() {        
        Vector<String> iso2 = new Vector<String>();
        for (String iso : Locale.getISOLanguages()) {
            Locale l = new Locale(iso);
            String iso3 = l.getISO3Language();

            boolean found = false;
            for (Locale locale : this.locales) {
                String _iso3 = locale.getISO3Language();
                if (_iso3.compareTo(iso3)==0) {
                    found = true;
                }
            }

            if (!found)
                if (!iso2.contains(iso3)) {
                    iso2.add(iso3);                        
                }
        }

        String [] tmp = iso2.toArray(new String[iso2.size()]);
        return tmp;
    }

    public String [] getDefinedLocales() {        
        Vector<String> iso2 = new Vector<String>();
        for (String iso : Locale.getISOLanguages()) {
            Locale l = new Locale(iso);
            String iso3 = l.getISO3Language();

            for (Locale locale : this.locales) {
                String _iso3 = locale.getISO3Language();
                if (_iso3.compareTo(iso3)==0) {
                    if (!iso2.contains(iso3)) {
                        iso2.add(iso3);                        
                    }
                }
            }
        }

        String [] tmp = iso2.toArray(new String[iso2.size()]);
        return tmp;
    }

    // =========== IMPORT =============
    
    // documentation on readme.org

    public String importFrom (int mode,boolean test,TMX tmx) {
        this.enableListener = false;        
        StringBuffer buf = new StringBuffer();
        buf.append("<html>");
        if (test) {
            buf.append("<i>Run as a test</i><br>");
        }


        if (mode==1 || mode==2) {
            buf.append("<b>Import new TUV</b>");
            buf.append("<br>");            
            buf.append(this.importTUV(tmx,test));
            buf.append("<br>");
        }
        if (mode==2) {
            buf.append("<b>Import new TU</b>");
            buf.append("<br>");            
            buf.append(this.importTU(tmx,test));
            buf.append("<br>");
        }     

        this.enableListener = true;
        this.modified();

        buf.append("</html>");
        return buf.toString();
    }

    private String importTUV (TMX tmx,boolean test) {
        StringBuffer buf = new StringBuffer();
        buf.append("<ul>");
        for (TMXTU tu : tmx.tus) {
            TMXTU ltu = this.findID(tu.getID());
            if (ltu!=null) {
                buf.append(ltu.importTUV(tu,test));
            }
        }        
        buf.append("</ul>");
        return buf.toString();
    }

    
    
    private String importTU (TMX tmx,boolean test) {
        StringBuffer buf = new StringBuffer();
        buf.append("<ul>");
        
        for (TMXTU tu : tmx.tus) {
            TMXTU ltu = this.findID(tu.getID());
            if (ltu==null) {
                if (!test) {
                    tu.duplicate(this);
                }
                buf.append("<li>import new TU :"+tu.getID()+"</li>");
            }
        }
        buf.append("</ul>");
        return buf.toString();
    }




}
